import md5 from 'md5-ts'
import { isJson } from './helper'
import { Config, Close, SendParams, QueueOption } from './interface'
const pkg = require('../package.json')
let Queue: QueueOption[] = []   // 消息队列
let Pool: any = {}              // Promise池
let IsOpen: boolean = false     // 是否打开,socket只会open一次哦
let IsClose: boolean = false    // 是否是主动关闭
let heartTimer: any = null      // 心跳句柄
let reConnectTimer: any = null   // 重连句柄
let IsLogin: boolean = false    // 是否登录connect成功,区别IsOpen
let IsNetWork: boolean = true   // 是否有网络
export default class PromiseWebSocket {
  config: Config
  initSocket: any
  send: any
  close: any
  constructor(config: Config) {
    console.log(`%c ${pkg.name} %c 版本: v${pkg.version} %c`,
                  'background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;  color: #fff',
                  'background:#41b883 ; padding: 1px; border-radius: 0 3px 3px 0;  color: #fff',
                  'background:transparent'
                )
    console.log(`%c ${pkg.name} %c 欢迎使用,满意的话打个赏呗 https://gitee.com/okcoder/uni-socket-promise %c`,
                  'background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;  color: #fff',
                  'background:#41b883 ; padding: 1px; border-radius: 0 3px 3px 0;  color: #fff',
                  'background:transparent'
                )
    this.config = {
      ...{
        debug: process.env.NODE_ENV === 'development',
        isReconnect: true,
        isHeartData: true,
        heartTime: 3000,
        reConnectTime: 3000
      }, ...config
    }
    // 初始化
    this.initSocket = (success: any, fail: any) => {
      if (IsLogin) {
        this.config.debug && console.log('%c [uni-socket-promise] %c 已经登录了', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
        typeof success === 'function' && success(this)
        return
      }
      // @ts-ignore
      uni.getNetworkType({
        fail: (err: any) => {
          this.config.debug && console.log('%c [socket] %c 检查网络状态失败:', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;', err)
          typeof fail === 'function' && fail(err, this)
        },
        success: (res: any) => {
          if (res.networkType === 'none') {
            IsNetWork = false
            IsLogin = false
            // @ts-ignore
            uni.showModal({
              title: '网络错误',
              content: '请打开网络服务',
              showCancel: false
            })
            typeof fail === 'function' && fail(res, this)
          } else {
            IsNetWork = true
            this.config.debug && console.log('%c [uni-socket-promise] %c 网络正常,开始建立连接...', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
            // @ts-ignore
            uni.connectSocket({
              url: this.config.url,
              method: this.config.method,
              header: this.config.header,
              protocols: this.config.protocols,
              success: () => {
                IsLogin = true
                this.config.debug && console.log('%c [uni-socket-promise] %c 连接成功', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
                typeof success === 'function' && success(this)
              },
              fail: (err: any) => {
                this.config.debug && console.log('%c [uni-socket-promise] %c 连接失败', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
                typeof fail === 'function' && fail(err, this)
              }
            })
          }
        }
      })
    }
    /**
     * 发送socket消息
     * @param string event 事件名称
     * @param object data  请求数据
     * @param object extraData  同级附加参数 注意event,data会被覆盖
     */
    this.send = (event: string, data?: object, extraData?: object) => {
      const nData: string | object = (!data || JSON.stringify(data) === '{}') ? '' : data
      let message = Object.assign(extraData || {}, { event, data: nData })
      if (typeof this.config.onSendMessageBefore === 'function') {
        message = this.config.onSendMessageBefore(message)
      }
      const uuid = md5(event + (isJson(nData) ? JSON.stringify(nData) : nData))
      return new Promise((resolve: any, reject: any) => {
        // 一摸一样的请求只响应第一次
        // 过滤了socket队列,否则队列重发送消息无法收到
        if (!Pool[uuid]) {
          Pool[uuid] = { message, resolve, reject }
        }
        if (IsOpen && IsLogin && IsNetWork) {
          this.config.debug && console.log('%c [uni-socket-promise] %c 发送消息:', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;', message)
          // @ts-ignore
          this._sendSocketMessage(message)
          typeof this.config.onSendMessageAfter === 'function' && this.config.onSendMessageAfter(message)
        } else {
          // 加入队列
          Queue.push({ uuid, message })
          this.config.debug && console.log('%c [uni-socket-promise] %c 加入消息队列', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;', message)
        }
      })
    }

    // 主动关闭
    this.close = (option: Close) => {
      // 目前还不清楚Z主动退出socket会不会导致open关闭 暂时默认不关闭
      // IsOpen = false;
      IsLogin = false
      // 主动退出
      IsClose = true
      if (this.config.isHeartData) {
        this.config.debug && console.log('%c [uni-socket-promise] %c 关闭心跳', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
        this._clearHeart()
      }
      // @ts-ignore
      uni.closeSocket(option)
      this.config.debug && console.log('%c [uni-socket-promise] %c 主动退出', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
    }

    // 监听socket是否打开成功
    // @ts-ignore
    uni.onSocketOpen((header: object) => {
      this.config.debug && console.log('%c [uni-socket-promise] %c socket打开成功', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
      typeof this.config.onSocketOpen === 'function' && this.config.onSocketOpen(header)
      IsOpen = true
      IsLogin = true
      // 判断是否需要发送心跳包
      if (this.config.isHeartData) {
        this.config.debug && console.log('%c [uni-socket-promise] %c 开始心跳', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
        this._clearHeart()
        this._startHeart()
      }
      // 发送消息队列消息
      for (let i: number = 0; i < Queue.length; i++) {
        this.config.debug && console.log('%c [uni-socket-promise] %c 发送队列消息:', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;', Queue[i].message)
        const uuid = Queue[i].uuid
        let message = Queue[i].message
        const event = message.event
        const data = message.data
        delete message.event
        delete message.data
        this.send(event, data, message).then((response: any) => {
          Pool[uuid].resolve(response)
        })
      }
      Queue = []
    })

    // 监听网络状态
    // @ts-ignore
    uni.onNetworkStatusChange((res: any) => {
      if (res.isConnected) {
        IsNetWork = true
        if (!IsLogin && this.config.isReconnect) {
          this.config.debug && console.log('%c [uni-socket-promise] %c 监听到有网络服务,重新连接socket', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
          this._reConnectSocket() // 连接成功IsLogin=true
        }
      } else {
        this.config.debug && console.error('%c [uni-socket-promise] %c 监听到没有网络状态', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
        IsLogin = false
        IsNetWork = false
        this.config.isHeartData && this._clearHeart()
        if (typeof this.config.onNetworkStatusNone === 'function') {
          this.config.onNetworkStatusNone()
        } else {
          // @ts-ignore
          uni.showModal({
            title: '网络错误',
            content: '请打开网络服务',
            showCancel: false
          })
        }
      }
    })

    // @ts-ignore
    uni.onSocketMessage((e: any) => {
      if (typeof this.config.onSocketMessageBefore === 'function' && !this.config.onSocketMessageBefore(e)) {
        // 如果onSocketMessageBefore返回false 那么终止下面的逻辑
        // 如果onSocketMessageBefore返回true 那么继续下面的逻辑
        return
      }
      const json: any = isJson(e.data)
      const uuid: string = json['event']
      if (!json || !uuid || !Pool[uuid]) return
      this.config.debug && console.log('%c [uni-socket-promise] %c 收到消息:', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;', json['data'])
      let data = json['data'] || null
      if (typeof this.config.onSocketMessageAfter === 'function') {
        this.config.onSocketMessageAfter(data,Pool[uuid])
      } else {
        Pool[uuid].resolve(data)
      }
      delete Pool[uuid]
    })

    // 监听socket被关闭
    // @ts-ignore
    uni.onSocketClose((res: any) => {
      this.config.debug && console.error('%c [uni-socket-promise] %c 连接被关闭', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;', res)
      typeof this.config.onSocketClose === 'function' && this.config.onSocketClose(res)
      IsLogin = false
      // 停止心跳检查
      if (this.config.isHeartData) {
        this.config.debug && console.log('%c [uni-socket-promise] %c 关闭心跳', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
        this._clearHeart()
      }
      if (!IsClose && this.config.isReconnect) {
        // 断线重连
        this._reConnectSocket()
      }
    })
    // 监听socket错误
    // @ts-ignore
    uni.onSocketError((res: any) => {
      this.config.debug && console.error('%c [uni-socket-promise] %c socket出错', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;', res)
      // 目前还不清楚socket出错会不会导致open关闭 暂时默认不关闭
      // IsOpen = false;
      IsLogin = false
      if (this.config.isReconnect) {
        this.initSocket()
      }
      typeof this.config.onSocketError === 'function' && this.config.onSocketError(res)
    })
  }
  // 发送socket消息
  _sendSocketMessage(data: SendParams) {
    return new Promise((resolve: any, reject: any) => {
      // @ts-ignore
      uni.sendSocketMessage({
        data: JSON.stringify(data),
        success: (res: any) => resolve(res),
        fail: (fail: any) => reject(fail)
      })
    })
  }
  // socket 重连
  _reConnectSocket() {
    if (!IsLogin) {
      reConnectTimer = setInterval(() => {
        this.config.debug && console.warn('%c [uni-socket-promise] %c 重新连接:', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
        this.initSocket(function (e: any) {
          if (e.config.isSendHeart) {
            e.config.debug && console.log('%c [uni-socket-promise] %c 开始心跳:', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;')
            e._clearHeart()
            e._startHeart()
          }
          clearInterval(reConnectTimer)
          reConnectTimer = null
        }, function (err: any, e: any) {
          e.config.debug && console.log('%c [socket] %c 重新连接失败', 'color:#fff;background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;', 'color:#000;',err)
        })
      }, this.config.reConnectTime)
    }
  }
  // 清理心跳
  _clearHeart() {
    clearInterval(heartTimer)
    heartTimer = null
  }
  // 开始心跳
  _startHeart() {
    heartTimer = setInterval(() => {
      // @ts-ignore
      uni.sendSocketMessage({
        data: 'ping'
      })
    }, this.config.heartTime)
  }
}
